package netcode.server;

import gamePlay.Bet;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListMap;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import netcode.exceptions.EndOfMessageException;
import netcode.exceptions.GameKeyException;
import netcode.helper.Message;
import netcode.helper.NetcodeHelper;

/**
 * This class represents the Server network code for the SwEng II 2012 project
 * at UW-Parkside. It uses UDP for message transmission.
 * 
 * @author Anthony Dare
 */
public class Server extends Thread {
	private static final boolean VERBOSE = true;

	private static DatagramSocket serverSocket;
	private static Map<Integer, NetworkGame> networkGameThreads;
	private int newGameID = Integer.MIN_VALUE;

	/**
	 * This method gets the server up and running. Every there are no
	 * parameters.
	 * 
	 * @param args
	 *            Not used.
	 */
	public static void main(String args[]) {
		// The port to be used for incoming packets on the server.

		try {
			if (VERBOSE) {
				System.out.println("Main: Creating Server...");
			}
			Server server = new Server(NetcodeHelper.SERVER_PORT);
			if (VERBOSE) {
				System.out.println("Main: Server Created.");
			}

			if (VERBOSE) {
				System.out.println("Main: Starting Server Thread...");
			}
			server.start();
			if (VERBOSE) {
				System.out.println("Main: Server Thread Started.");
			}

			if (VERBOSE) {
				System.out.println("Main: Waiting For Server Thread To End...");
			}
			try {
				server.join();
				if (VERBOSE) {
					System.out.println("Main: Server Thread Ended.");
				}
			} catch (Exception e) {
				System.err.println(e);
				if (VERBOSE) {
					System.out
							.println("Main: Problem Occurred Ending Server Thread.");
				}
			}

		} catch (Exception e) {
			System.err.println(e);
		}
	}

	public Server(int port) throws Exception {
		super();
		serverSocket = new DatagramSocket(port);

		networkGameThreads = new ConcurrentSkipListMap<Integer, NetworkGame>();
	}

	@Override
	public void run() {
		byte receivingBuffer[] = new byte[256];
		DatagramPacket receivedPacket = new DatagramPacket(receivingBuffer,
				receivingBuffer.length);
		if (VERBOSE) {
			System.out.println("Server: Entering Loop...");
		}
		while (true) {
			if (VERBOSE) {
				System.out.println("Server: Receiving Packet...");
			}
			try {
				serverSocket.receive(receivedPacket);
				if (VERBOSE) {
					System.out.println("Server: Packet Received.");
				}

				if (VERBOSE) {
					System.out.println("Server: Processing Packet...");
				}
				if (receivedPacket.getData()[NetcodeHelper.BB_MESSAGE_ID_INDEX] == NetcodeHelper.BRAINS_AND_BETS) {
					if (receivedPacket.getData()[NetcodeHelper.BB_MESSAGE_VERSION_INDEX] == NetcodeHelper.MESSAGE_VERSION) {
						/*
						 * Implement the catching of packets here. Just add a
						 * new case, with the appropriate NetcodeHelper code,
						 * and call the appropriate handler method, passing in
						 * whatever info is needed.
						 */
						switch (receivedPacket.getData()[NetcodeHelper.BB_MESSAGE_TYPE_INDEX]) {
						case NetcodeHelper.CONNECTION_REQUEST:
							handleConnectionRequest(receivedPacket);
							break;
						case NetcodeHelper.JOIN_GAME_REQUEST:
							handleJoinGameRequest(receivedPacket);
							break;
						case NetcodeHelper.SERVER_LOBBY_INFO_REQUEST:
							handleServerLobbyInfoRequest(receivedPacket);
							break;
						case NetcodeHelper.CREATE_GAME_REQUEST:
							handleCreateGameRequest(receivedPacket);
							break;
						case NetcodeHelper.START_GAME_REQUEST:
							handleStartGameRequest(receivedPacket);
							break;
						case NetcodeHelper.ANSWER:
							handleAnswer(receivedPacket);
							break;
						case NetcodeHelper.SUBMIT_BETS:
							handleSubmitBets(receivedPacket);
							break;
						case NetcodeHelper.NEXT_ROUND_REQUEST:
							handleNextRoundRequest(receivedPacket);
							break;
						case NetcodeHelper.LEAVE_GAME_REQUEST:
							handleLeaveGameRequest(receivedPacket);
							break;
						case NetcodeHelper.DISCONNECT:
							handleDisconnect(receivedPacket);
							break;
						case NetcodeHelper.ARE_YOU_THERE:
							handleAreYouThere(receivedPacket);
							break;
						case NetcodeHelper.JOIN_GAME_INVITE:
							handleJoinGameInvite(receivedPacket);
							break;
						default:
							sendError(receivedPacket.getAddress(),
									receivedPacket.getPort(),
									NetcodeHelper.ERROR_BAD_TYPE,
									"Invalid Message Type. Try updating your game.");
							break;
						}
						/*
						 * End of packet catching.
						 */
					} else {
						sendError(
								receivedPacket.getAddress(),
								receivedPacket.getPort(),
								NetcodeHelper.ERROR_BAD_VER,
								"Please upgrade to the latest Brains and Bets app if you would like to play online.");
					}
				} else {
					sendError(receivedPacket.getAddress(),
							receivedPacket.getPort(),
							NetcodeHelper.ERROR_BAD_MESSAGE,
							"Invalid Message Type. You're not a Brains and Bets client.");
				}
			} catch (Exception e) {
				System.err.println(e);
				if (VERBOSE) {
					System.out
							.println("Server: Problem Occurred receiving packet.");
				}
			}
		}
	}

	/*
	 * Implement handlers and message sending methods below.
	 */
	/**
	 * Takes in a client CONNECTION_REQUEST, generates a connectedClient object,
	 * and returns the clientKey to the client.
	 * 
	 * @param packet
	 */
	private void handleConnectionRequest(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("CONNECTION_REQUEST received:");
		}
		// Using the Message object
		Message receivedMessage = new Message(packet);
		if (receivedMessage.hasNext()) {
			try {
				String name = receivedMessage.nextString();
				if (VERBOSE) {
					System.out.println("Name: " + name);
					System.out
							.println("Adding client to connectedClients Map...");
				}
				Integer clientKey = ConnectedClientFactory
						.generateConnectedClient(packet.getAddress(),
								packet.getPort(), name);
				if (VERBOSE) {
					System.out
							.println("Client Added. Client Key: " + clientKey);
					System.out
							.println("Sending CONNECTION_RESPONSE to client...");
				}

				sendConnectionResponse(clientKey);

			} catch (EndOfMessageException e) {
				System.err.println("Unexpected End of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message, unable to connect.");
			}
		}
	}

	public static void sendConnectionResponse(int clientKey) {
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);
		Message sendMessage = new Message(NetcodeHelper.CONNECTION_RESPONSE);
		sendMessage.add(clientKey);
		sendMessage.finalize();
		DatagramPacket responsePacket = new DatagramPacket(
				sendMessage.getData(), sendMessage.getDataLength(),
				client.getAddress(), client.getPort());
		try {
			serverSocket.send(responsePacket);
			if (VERBOSE) {
				System.out.println("Server: CONNECTION_RESPONSE sent.");
			}
		} catch (IOException e) {
			if (VERBOSE) {
				System.err
						.println("Server: Error sending CONNECTION_RESPONSE.");
			}
			System.err.println(e);
		}
	}

	/**
	 * Handles the JOIN_GAME_REQUEST from the client, adding the client
	 * specified by the key to the game specified by the gameID. This method is
	 * complete
	 */
	private void handleJoinGameRequest(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("JOIN_GAME_REQUEST received:");
		}
		Message message = new Message(packet);
		if (message.hasNext()) {

			int clientKey;
			try {
				clientKey = message.nextInt();
				if (VERBOSE) {
					System.out.println("clientKey: " + clientKey);
				}
				if (ConnectedClientFactory.getConnectedClient(clientKey) == null) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_CLIENTID,
							"Invalid Client Key");
					return;
				}
			} catch (EndOfMessageException e) {
				System.err
						.println("Unable to get ClientID. Unexpected end of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message. Missing Client Key");
				return;
			}

			int gameID;
			try {
				gameID = message.nextInt();
				if (networkGameThreads.containsKey(gameID) == false) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_GAMEID, "Invalid Game Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("gameID: " + gameID);
				}
			} catch (EndOfMessageException e) {
				System.err
						.println("Unable to get GameID. Unexpected end of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message. Missing GameKey");
				return;
			}

			NetworkGame game = networkGameThreads.get(gameID);
			// Checking to see if the game is in the appropriate state to add a
			// client.
			// That is, waiting for players or started. Basically, not ended.
			if (game.getGameState() == NetworkGame.WAITING_TO_START
					|| game.getGameState() == NetworkGame.STARTED) {
				if (VERBOSE) {
					System.out.println("Game Found, name: "
							+ game.getGameName());
				}
				if (game.getNConnectedClients() >= NetworkGame.MAX_N_PLAYERS) {
					if (VERBOSE) {
						System.err
								.println("Too many players in the game already. Can't add client.");
					}
					// TODO Send an error here.
				} else {
					game.addClient(clientKey, ConnectedClientFactory
							.getConnectedClient(clientKey));
					if (VERBOSE) {
						System.out.println("Client added to game.");
					}
				}
			} else {
				// TODO Game already ended. Send a more appropriate error here.
				// Joining a game in progress is a feature requested by the
				// client.
				sendError(clientKey, NetcodeHelper.ERROR_GAME_IN_PROGRESS,
						"The game has already begun. Sorry.");
				if (VERBOSE) {
					System.out
							.println("Game is already ended. Unable to add client.");
				}
			}
			// Going leave this out for now.
			// sendMessageReceived(clientKey, message.getMessageType());
		}
	}

	// *******************************************************
	// Keep in mind the parameters of these methods are not
	// set in stone. If you see the need to change them,
	// please do so.
	// *******************************************************

	/*
	 * Handles Packet for server lobby information COMPLETE
	 * 
	 * @author Nicole
	 */
	private void handleServerLobbyInfoRequest(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("SERVER_LOBBY_INFO_REQUEST received:");
		}
		// Convert packet to message
		Message message = new Message(packet);

		// Read message
		if (message.hasNext()) {
			try {
				// Get client key from message
				int clientKey = message.nextInt();
				if (ConnectedClientFactory.getConnectedClient(clientKey) == null) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_CLIENTID,
							"Invalid Client Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("clientKey: " + clientKey);
				}
				// send new message to client.
				sendServerLobbyInfoResponse(clientKey);
			} catch (EndOfMessageException e) {
				System.err.println("Request is missing client key.");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected end of Message. Missing ClientKey");
			}

		}
	}

	/**
	 * Format for sending server lobby info
	 * <<GameID;GameName;Player1;Player2;Player3
	 * ;Player4;...><GameID;GameName2;Player1;...>...> COMPLETE
	 * 
	 * (Anthony) Was getting string index errors on the client side. Keeping
	 * original code in comments, but rewriting.
	 * 
	 * @author Nicole
	 */
	public static void sendServerLobbyInfoResponse(int clientKey) {
		if (VERBOSE) {
			System.out.println("SERVER_LOBBY_INFO_RESPONSE creation:");
		}
		// Create message to send to client
		Message sendMessage = new Message(
				NetcodeHelper.SERVER_LOBBY_INFO_RESPONSE);
		// Get client
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);

		// (Anthony) Code Begins
		Iterator<Integer> gameIterator = networkGameThreads.keySet().iterator();
		while (gameIterator.hasNext()) {
			NetworkGame game = networkGameThreads.get(gameIterator.next());
			if (game.getGameState() == NetworkGame.WAITING_TO_START
					|| game.getGameState() == NetworkGame.STARTED) {
				sendMessage.add(game.getGameID());
				if (VERBOSE) {
					System.out.println("gameID: " + game.getGameID());
				}
				sendMessage.add(game.getGameName());
				if (VERBOSE) {
					System.out.println("gameName: " + game.getGameName());
				}
				sendMessage.add(game.getGameState());
				if (VERBOSE) {
					System.out.println("gameState: " + game.getGameState());
				}
			}
		}
		sendMessage.finalize();
		DatagramPacket sendPacket = new DatagramPacket(sendMessage.getData(),
				sendMessage.getDataLength(), client.getAddress(),
				client.getPort());
		try {
			serverSocket.send(sendPacket);
			if (VERBOSE) {
				System.out.println("SERVER_LOBBY_INFO_RESPONSE sent.");
			}
		} catch (IOException e) {
			if (VERBOSE) {
				System.out.println("Error sending SERVER_LOBBY_INFO_RESPONSE.");
			}
			e.printStackTrace();
		}
		// (Anthony) Code Ends

		// // Create string of lobby info
		// String info = "<";
		// // Get all keys from map
		// Set<Integer> gameIds = networkGameThreads.keySet();
		// Iterator<Integer> it = gameIds.iterator();
		// int gameId;
		// while (it.hasNext()) {
		// gameId = it.next();
		// NetworkGame game = networkGameThreads.get(gameId);
		// info += "<" + gameId + ";" + game.getName() + ";";
		//
		// // Add player info
		// for (int i = 0; i < game.getConnectedClients().size(); i++) {
		// info += game.getConnectedClients().get(i).getName() + ";";
		// }
		// info += ">";
		// }
		// // Initial <> wrapper allows client to easily tell if no games are in
		// // the lobby
		// info += ">";
		// // Finish message
		// sendMessage.add(info);
		// sendMessage.finalize();
		// // Create packet to client
		// DatagramPacket responsePacket = new DatagramPacket(
		// sendMessage.getData(), sendMessage.getDataLength(),
		// client.getAddress(), client.getPort());
		// try {
		// // Send packet
		// serverSocket.send(responsePacket);
		// if (VERBOSE) {
		// System.out.println("Server: SERVER_LOBBY_INFO_RESPONSE sent.");
		// }
		// } catch (Exception e) {
		// System.err.println(e);
		// if (VERBOSE) {
		// System.out
		// .println("Server: Error sending SERVER_LOBBY_INFO_RESPONSE.");
		// }
		// }

	}

	/*
	 * This method will send information on a particular game using the format:
	 * Game ID PlayerName1 PlayerName2 COMPLETE
	 * 
	 * @author Nicole
	 */
	public static void sendGameLobbyInfo(int clientKey, int gameID) {
		// Create message to send to client
		Message sendMessage = new Message(NetcodeHelper.GAME_LOBBY_INFO);
		// Get client
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);
		// Add lobby info
		if (VERBOSE) {
			System.out.println("Sending Game Lobby Info with Information");
			System.out.println("  Client Key: " + clientKey);
			System.out.println("  Game ID: " + gameID);
			
		}
		sendMessage.add(gameID);
		sendMessage.add(networkGameThreads.get(gameID).getGameName());
		try {
			if (networkGameThreads.containsKey(gameID)) {
				Map<Integer, ConnectedClient> map = networkGameThreads.get(
						gameID).getConnectedClients();
				Set<Integer> pIds = map.keySet();
				Iterator<Integer> it = pIds.iterator();
				while (it.hasNext()) {
					int i = it.next();
					sendMessage.add(map.get(i).getName());
					if (VERBOSE) {
						System.out.println("  Players: ");
						System.out.println(map.get(i).getName());
					}
				}
			} else {
				throw new GameKeyException();
			}
		} catch (GameKeyException e) {
			System.err.println(e);
			if (VERBOSE) {
				System.err.println("Server: Error Game ID: " + gameID
						+ " Not Found.");
			}
		}

		sendMessage.finalize();
		// Create packet to client
		DatagramPacket responsePacket = new DatagramPacket(
				sendMessage.getData(), sendMessage.getDataLength(),
				client.getAddress(), client.getPort());
		try {
			// Send packet
			serverSocket.send(responsePacket);
			if (VERBOSE) {
				System.out.println("Server: GAME_LOBBY_INFO sent.");
			}
		} catch (IOException e) {
			System.err.println(e);
			if (VERBOSE) {
				System.out.println("Server: Error sending GAME_LOBBY_INFO.");
			}
		}
	}

	/*
	 * COMPLETE Format of message:
	 * <clientkey;clientName;gameName;rounds;category
	 * [0];categoryLength;category[1];category[2]...>
	 * 
	 * (Anthony) Getting string index errors. Refactoring.
	 * 
	 * @author Nicole
	 */
	private void handleCreateGameRequest(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("CREATE_GAME_REQUEST received:");
		}
		if (newGameID == Integer.MAX_VALUE) {
			newGameID = Integer.MIN_VALUE;
			while (networkGameThreads.containsKey(newGameID)) {
				newGameID++;
			}
		} else {
			newGameID++;
		}

		// Convert packet to message
		Message message = new Message(packet);
		NetworkGame game;

		// (Anthony) Code Begins
		try {
			if (message.hasNext()) {
				int clientKey = message.nextInt();
				if (VERBOSE) {
					System.out.println("clientKey: " + clientKey);
				}
				if (ConnectedClientFactory.getConnectedClient(clientKey) == null) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_CLIENTID,
							"Invalid Client Key");
					return;
				}
				String gameName = message.nextString();
				if (VERBOSE) {
					System.out.println("gameName: " + gameName);
				}
				int rounds = message.nextInt();
				if (VERBOSE) {
					System.out.println("rounds: " + rounds);
				}
				List<String> categories = new ArrayList<String>();
				while (message.hasNext()) {
					String category = message.nextString();
					categories.add(category);
					if (VERBOSE) {
						System.out.println("category: " + category);
					}
				}
				ConnectedClient client = ConnectedClientFactory
						.getConnectedClient(clientKey);
				if (client != null) {
					if (categories.size() > 0) {
						String catArray[] = new String[categories.size()];
						catArray = categories.toArray(catArray);
						game = new NetworkGame(newGameID, gameName, clientKey,
								client, catArray, rounds);

					} else {
						game = new NetworkGame(newGameID, gameName, clientKey,
								client, new String[0], rounds);
					}
					if (VERBOSE) {
						System.out.println("Game Created.");
					}
					networkGameThreads.put(newGameID, game);
					if (VERBOSE) {
						System.out
								.println("Game added to networkGameThreads Map.");
					}
					Server.sendGameLobbyInfo(clientKey, newGameID);
				} else {
					if (VERBOSE) {
						System.out
								.println("For whatever reason, the clientKey is not in the map. Could not create game.");
					}
				}
			}
		} catch (EndOfMessageException e) {
			System.err.println("Unexpected End of Message");
			e.printStackTrace();
			sendError(packet.getAddress(), packet.getPort(),
					NetcodeHelper.ERROR_END_OF_MESSAGE,
					"Unexpected End of Message, unable to connect.");
		}

		// (Anthony Code Ends

		// // Read message
		// if (message.hasNext()) {
		// try {
		//
		// if (VERBOSE) {
		// System.out.println("Server Handling Packet Information");
		// }
		// // Get client key from message
		// String createGameMessageInfo = message.nextString();
		// int start = createGameMessageInfo.indexOf("<");
		// int end = createGameMessageInfo.indexOf(";");
		// // Get Client ID
		// int clientId = Integer.parseInt(createGameMessageInfo
		// .substring(start + 1, end));
		// // Remove client id from string removing ;
		// createGameMessageInfo = createGameMessageInfo
		// .substring(end + 1);
		// // set new end point
		// end = createGameMessageInfo.indexOf(';');
		//
		// // get client name from string
		// String clientName = createGameMessageInfo.substring(0, end);
		// // Remove client name from message string and set new end point
		// createGameMessageInfo = createGameMessageInfo
		// .substring(end + 1);
		// end = createGameMessageInfo.indexOf(';');
		//
		// // get game name from string
		// String gameName = createGameMessageInfo.substring(0, end);
		// // Remove game name from string and set new end point
		// createGameMessageInfo = createGameMessageInfo
		// .substring(end + 1);
		// end = createGameMessageInfo.indexOf(';');
		//
		// // Get rounds from string
		// int rounds = Integer.parseInt(createGameMessageInfo.substring(
		// 0, end));
		// // Remove rounds from string and set new end point
		// createGameMessageInfo = createGameMessageInfo
		// .substring(end + 1);
		// end = createGameMessageInfo.indexOf(';');
		//
		// // Get # of Categories
		// int catLength = Integer.parseInt(createGameMessageInfo
		// .substring(0, end));
		// // Remove # of cats from string and set new end point
		// createGameMessageInfo = createGameMessageInfo
		// .substring(end + 1);
		// end = createGameMessageInfo.indexOf(';');
		//
		// // Check for # of categories
		// if (catLength > 0) {
		// String[] cats = new String[catLength];
		// // Get all categories
		// for (int i = 0; i < catLength; i++) {
		// cats[i] = createGameMessageInfo.substring(0, end);
		// // Set new end point and remove taken category
		// createGameMessageInfo = createGameMessageInfo
		// .substring(end + 1);
		// end = createGameMessageInfo.indexOf(';');
		// }
		// // Create new NetworkGame
		// game = new NetworkGame(
		// newGameID,
		// gameName,
		// clientId,
		// ConnectedClientFactory.getConnectedClient(clientId),
		// cats, rounds);
		// if (VERBOSE) {
		// System.out.println("Create Game Information:");
		// System.out.println("Client Key: " + clientId);
		// System.out.println("Client Name: "
		// + ConnectedClientFactory.getConnectedClient(
		// clientId).getName());
		// System.out.println("Game Name: " + gameName);
		// System.out.println("Game ID: " + newGameID);
		// System.out.println("Rounds: " + rounds);
		// System.out.println("Categories: ");
		// for (int i = 0; i < catLength; i++) {
		// System.out.println(cats[i]);
		// }
		// }
		// } else {
		// // Create Game with null categories
		// game = new NetworkGame(
		// newGameID,
		// gameName,
		// clientId,
		// ConnectedClientFactory.getConnectedClient(clientId),
		// null, rounds);
		// if (VERBOSE) {
		// System.out.println("Create Game Information:");
		// System.out.println("Client Key: " + clientId);
		// System.out.println("Client Name: "
		// + ConnectedClientFactory.getConnectedClient(
		// clientId).getName());
		// System.out.println("Game Name: " + gameName);
		// System.out.println("Game ID: " + newGameID);
		// System.out.println("Rounds: " + rounds);
		// System.out.println("Categories: None");
		// }
		// }
		//
		// networkGameThreads.put(newGameID, game);
		//
		// sendMessageReceived(clientId, NetcodeHelper.CREATE_GAME_REQUEST);
		//
		// } catch (Exception e) {
		// System.err.println(e);
		// }
		//
		// }
	}

	// Jim C
	private void handleStartGameRequest(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("START_GAME_REQUEST received:");
		}
		// Using the Message object
		Message receivedMessage = new Message(packet);
		if (receivedMessage.hasNext()) {
			try {
				int clientID = receivedMessage.nextInt();
				if (ConnectedClientFactory.getConnectedClient(clientID) == null) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_CLIENTID,
							"Invalid Client Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("clientKey: " + clientID);
				}
				int gameID = receivedMessage.nextInt();
				if (networkGameThreads.containsKey(gameID) == false) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_GAMEID, "Invalid Game Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("gameID: " + gameID);
				}

				NetworkGame game = networkGameThreads.get(gameID);
				if (game != null) {

					if (VERBOSE) {
						System.out.println("Starting game...");
					}
					if (!game.isAlive()) {
						if (game.getNConnectedClients() < NetworkGame.MIN_N_PLAYERS) {
							if (VERBOSE) {
								System.err
										.println("Not enough players to start game.");
							}
							// Should send an error here.
						} else {
							game.start();
							if (VERBOSE) {
								System.out.println("Game started.");
							}
						}
					} else if (VERBOSE) {
						System.out.println("Game has already been started.");
					}
				} else {
					if (VERBOSE) {
						System.err.println("Game not found. Unable to start.");
					}
					// Should probably send an error to the client here
				}
			} catch (EndOfMessageException e) {
				System.err.println("Unexpected End of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message, unable to connect.");
			}

		}
	}

	// Jim C
	public static void sendQuestion(int clientKey, String question) {
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);
		Message sendMessage = new Message(NetcodeHelper.QUESTION);
		sendMessage.add(question);
		sendMessage.finalize();
		DatagramPacket responsePacket = new DatagramPacket(
				sendMessage.getData(), sendMessage.getDataLength(),
				client.getAddress(), client.getPort());
		try {
			serverSocket.send(responsePacket);
			if (VERBOSE) {
				System.out.println("Server: Sending Question packet.");
			}

		} catch (IOException e) {
			System.err.println(e);
			if (VERBOSE) {
				System.out.println("Server: Error sending packet.");
			}
		}
	}

	// Jim C
	private void handleAnswer(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("ANSWER received:");
		}
		// Using the Message object
		Message receivedMessage = new Message(packet);
		if (receivedMessage.hasNext()) {
			try {
				int clientID = receivedMessage.nextInt();
				if (ConnectedClientFactory.getConnectedClient(clientID) == null) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_CLIENTID,
							"Invalid Client Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("clientKey: " + clientID);
				}
				int gameID = receivedMessage.nextInt();
				if (networkGameThreads.containsKey(gameID) == false) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_GAMEID, "Invalid Game Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("gameID: " + gameID);
				}
				int answer = receivedMessage.nextInt();
				if (VERBOSE) {
					System.out.println("answer: " + answer);
				}

				NetworkGame game = networkGameThreads.get(gameID);
				// This seems to cause Exceptions
				// game.interrupt();
				game.clientSubmitAnswer(clientID, answer);
				if (VERBOSE) {
					System.out.println("Answer submitted.");
				}

			} catch (EndOfMessageException e) {
				System.err.println("Unexpected End of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message, unable to connect.");
			}
		}
	}

	/**
	 * Sends the list of betting odds to all players in
	 * the game in the format: Game ID, Odds 1, Odds 2, Odds 3, ...
	 * 
	 * by Jim J.
	 * 
	 * @param clientKey
	 *            client to send info to
	 * @param gameId
	 *            game identifier
	 * @param odds
	 *            list of odds
	 */
	public static void sendBettingOdds(int clientKey, int gameId,
			ArrayList<Integer> odds, ArrayList<Integer> guesses) {
		if (VERBOSE) {
			System.out.println("Creating Message to Send Betting Odds....");
		}
		Message sendMessage = new Message(NetcodeHelper.BETTING_ODDS);
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);

		if (VERBOSE) {
			System.out.println("Guess\t\tOdd");
		}
		for (int i = 0; i < odds.size(); i++) {
			sendMessage.add(guesses.get(i));
			sendMessage.add(odds.get(i));
			if (VERBOSE) {
				System.out.print(guesses.get(i) + "\t\t");
				System.out.println(odds.get(i));
			}
		}

		sendMessage.finalize();

		DatagramPacket sendPacket = new DatagramPacket(sendMessage.getData(),
				sendMessage.getDataLength(), client.getAddress(),
				client.getPort());

		try {
			serverSocket.send(sendPacket);

			if (VERBOSE)
				System.out.println("Server: betting odds sent.");

		} catch (IOException e) {
			System.err.println(e);

			if (VERBOSE)
				System.out
						.println("Server: error sending betting odds to client: "
								+ clientKey);
		}
	}

	/**
	 * Takes received packet, parses the bet info from it,
	 * and registers it with the NetworkGame
	 * 
	 * by Jim J
	 * 
	 * @param packet
	 *            received data packet
	 */
	private void handleSubmitBets(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("SUBMIT_BETS received:");
		}
		Message receivedMessage = new Message(packet);
		ArrayList<Bet> bets = new ArrayList<Bet>();
		if (receivedMessage.hasNext()) {
			try {
				int clientID = receivedMessage.nextInt();
				if (ConnectedClientFactory.getConnectedClient(clientID) == null) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_CLIENTID,
							"Invalid Client Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("clientKey: " + clientID);
				}
				int gameID = receivedMessage.nextInt();
				if (networkGameThreads.containsKey(gameID) == false) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_GAMEID, "Invalid Game Key");
					return;
				}
				if (VERBOSE) {
					System.out.println("gameID: " + gameID);
				}
				NetworkGame game = networkGameThreads.get(gameID);
				while (receivedMessage.hasNext()) {

					int choice = receivedMessage.nextInt();
					if (VERBOSE) {
						System.out.println("choice: " + choice);
					}
					int permaChips = receivedMessage.nextInt();
					if (VERBOSE) {
						System.out.println("permaChips: " + permaChips);
					}
					int gameChips = receivedMessage.nextInt();
					if (VERBOSE) {
						System.out.println("gameChips: " + gameChips);
					}

					bets.add(new Bet("", choice, permaChips, gameChips));
				}
				game.clientSubmitBet(clientID, bets);

				if (VERBOSE) {
					System.out.println("Server: submitted bets.");
				}

			} catch (EndOfMessageException e) {
				System.err.println("Unexpected End of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message, unable to connect.");
			}
		}
	}

	/**
	 * Sends the round's results to each player in the
	 * format: Game ID, Player1 name, Player1 payout, Player2 name, Player2
	 * payout, ...
	 * 
	 * by Jim J
	 * 
	 * @param clientKey
	 *            client to send round results to
	 * @param gameId
	 *            game identifier
	 * @param results
	 *            int array of client ID's and their results
	 */
	public static void sendRoundResults(int clientKey, int gameId, int answer,
			int[] results) {
		if (VERBOSE) {
			System.out.println("ROUND_RESULTS creation:");
		}
		Message sendMessage = new Message(NetcodeHelper.ROUND_RESULTS);
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);

		sendMessage.add(gameId);
		if (VERBOSE) {
			System.out.println("gameID: " + gameId);
		}

		sendMessage.add(answer);
		if (VERBOSE) {
			System.out.println("answer: " + answer);
		}

		for (int i = 0; i < results.length; i += 2) {
			String pName = ConnectedClientFactory
					.getConnectedClient(results[i]).getName();

			sendMessage.add(pName);
			if (VERBOSE) {
				System.out.println("playerName: " + pName);
			}

			sendMessage.add(results[i + 1]);
			if (VERBOSE) {
				System.out.println("result: " + results[i + 1]);
			}
		}

		sendMessage.finalize();

		DatagramPacket sendPacket = new DatagramPacket(sendMessage.getData(),
				sendMessage.getDataLength(), client.getAddress(),
				client.getPort());

		try {
			serverSocket.send(sendPacket);

			if (VERBOSE)
				System.out.println("Server: round results sent.");

		} catch (IOException e) {
			System.err.println(e);

			if (VERBOSE)
				System.out
						.println("Server: error sending round results to client: "
								+ clientKey);
		}

	}

	// Adam P.
	private void handleNextRoundRequest(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("NEXT_ROUND_REQUEST received.");
		}
		// Using the Message object
		Message receivedMessage = new Message(packet);
		if (receivedMessage.hasNext()) {
			try {
				// int clientID = receivedMessage.nextInt();
				int gameID = receivedMessage.nextInt();

				if (networkGameThreads.containsKey(gameID) == false) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_GAMEID, "Invalid Game Key");
					return;
				}

				networkGameThreads.get(gameID).notifyAll();

				if (VERBOSE) {
					System.out.println("Server: Receiving Next Round packet.");
				}
			} catch (EndOfMessageException e) {
				System.err.println("Unexpected End of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message, unable to connect.");
			}
		}
	}

	// Adam P.
	private void handleLeaveGameRequest(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("LEAVE_GAME_REQUEST received:");
		}
		// Using the Message object
		Message receivedMessage = new Message(packet);
		if (receivedMessage.hasNext()) {
			try {
				int clientID = receivedMessage.nextInt();
				if (ConnectedClientFactory.getConnectedClient(clientID) == null) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_CLIENTID,
							"Invalid Client Key");
					return;
				}
				int gameID = receivedMessage.nextInt();
				if (networkGameThreads.containsKey(gameID) == false) {
					sendError(packet.getAddress(), packet.getPort(),
							NetcodeHelper.ERROR_BAD_GAMEID, "Invalid Game Key");
					return;
				}

				NetworkGame game = networkGameThreads.get(gameID);
				game.removeClient(clientID);
				if (game.getConnectedClients().isEmpty()) {
					// Remove game from game list
					networkGameThreads.remove(game.getGameID());
					// Destroy Game
					// As far as I can tell, the thread doesn't keep running
					// forever.
					// If it does, then we need an interrupt method.
				}

				if (VERBOSE) {
					System.out.println("Server: Client removed from game "
							+ clientID);
				}
			} catch (EndOfMessageException e) {
				System.err.println("Unexpected End of Message");
				e.printStackTrace();
				sendError(packet.getAddress(), packet.getPort(),
						NetcodeHelper.ERROR_END_OF_MESSAGE,
						"Unexpected End of Message, unable to connect.");
			}
		}
	}

	// Adam P., Jim J.
	public static void sendGameResults(int clientKey, int gameId,
			int[] gameResults) {

		Message message = new Message(NetcodeHelper.GAME_RESULTS);
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);

		message.add(gameId);
		if (VERBOSE) {
			System.out.println("gameID: " + gameId);
		}

		for (int i = 0; i < gameResults.length; i += 2) {
			String playerName = ConnectedClientFactory.getConnectedClient(
					gameResults[i]).getName();

			message.add(playerName);
			if (VERBOSE) {
				System.out.println("playerName: " + playerName);
			}

			message.add(gameResults[i + 1]);
			if (VERBOSE) {
				System.out.println("result: " + gameResults[i + 1]);
			}
		}

		message.finalize();

		DatagramPacket packet = new DatagramPacket(message.getData(),
				message.getDataLength(), client.getAddress(), client.getPort());

		try {
			serverSocket.send(packet);

			if (VERBOSE)
				System.out.println("Server: game results sent.");
		} catch (IOException e) {
			System.out.println("Server: error sending game results to client: "
					+ clientKey);
		}
	}

	private void handleDisconnect(DatagramPacket packet) {
		if (VERBOSE) {
			System.out.println("DISCONNECT received:");
		}
		Message message = new Message(packet);
		int clientKey = 0;
		try {
			clientKey = message.nextInt();
		} catch (EndOfMessageException e) {
			System.err.println("Unexpected End of Message");
			e.printStackTrace();
			sendError(packet.getAddress(), packet.getPort(),
					NetcodeHelper.ERROR_END_OF_MESSAGE,
					"Unexpected End of Message, unable to connect.");
		}
		ConnectedClientFactory.disconnectClient(clientKey);
	}

	public static void sendAreYouThere(int clientKey) {

		try {
			if (VERBOSE) {
				System.out.println("Client: Creating Ping");
			}

			// Using the Message object implementation
			Message message = new Message(NetcodeHelper.ARE_YOU_THERE);
			message.add(clientKey);
			message.finalize();
			ConnectedClient client = ConnectedClientFactory
					.getConnectedClient(clientKey);
			DatagramPacket packet = new DatagramPacket(message.getData(),
					message.getDataLength(), client.getAddress(),
					client.getPort());
			serverSocket.send(packet);

			if (VERBOSE) {
				System.out.println("Client: Pinged");
			}

		} catch (IOException e) {
			System.err.println(e);
		}

	}

	// Pretty much only acts as a wrapper for sendMessageReiceved.
	// Currently can only work with connected clients since message received
	// only works with connected clients
	private void handleAreYouThere(DatagramPacket packet) {
		Message message = new Message(packet);
		int clientKey = 0;
		try {
			clientKey = message.nextInt();
		} catch (EndOfMessageException e) {
			System.err.println("Unexpected End of Message");
			e.printStackTrace();
			sendError(packet.getAddress(), packet.getPort(),
					NetcodeHelper.ERROR_END_OF_MESSAGE,
					"Unexpected End of Message, unable to connect.");
		}
		sendMessageReceived(clientKey, NetcodeHelper.ARE_YOU_THERE);
	}

	// Sends an error because of a bad message
	private void sendError(int clientKey, byte errorType, String errorMessage) {
		ConnectedClient client = ConnectedClientFactory
				.getConnectedClient(clientKey);
		sendError(client.getAddress(), client.getPort(), errorType,
				errorMessage);
	}

	// Second form for when client key is not available
	private void sendError(InetAddress ipAddress, int portNumber,
			byte errorType, String errorMessage) {
		try {

			if (VERBOSE) {
				System.out.println("Building Error...");
			}

			Message message = new Message(NetcodeHelper.ERROR);
			message.add(errorType);
			message.add(errorMessage);
			message.finalize();

			if (VERBOSE) {
				System.out.println("Sending Error...");
			}

			DatagramPacket packet = new DatagramPacket(message.getData(),
					message.getDataLength(), ipAddress, portNumber);
			serverSocket.send(packet);

			if (VERBOSE) {
				System.out.println("Error Sent");
			}

		} catch (IOException e) {
			System.err.println("Unab;e to send packet.");
			e.printStackTrace();
			return;
		}

	}

	public static void sendMessageReceived(int clientKey, byte messageType) {
		try {
			if (VERBOSE) {
				System.out.println("Sending MESSAGE_RECEIVED message...");
			}
			Message message = new Message(NetcodeHelper.MESSAGE_RECEIVED);
			message.add((int) messageType);
			message.finalize();
			ConnectedClient client = ConnectedClientFactory
					.getConnectedClient(clientKey);
			DatagramPacket packet = new DatagramPacket(message.getData(),
					message.getDataLength(), client.getAddress(),
					client.getPort());
			serverSocket.send(packet);
			if (VERBOSE) {
				System.out.println("MESSAGE_RECEIVED message sent.");
			}
		} catch (IOException e) {
			System.err.println(e);
		}
	}
	
	
	/*
	 * Handles a request from client to send email invite to join a game
	 * Nicole
	 */
	public static void handleJoinGameInvite(DatagramPacket packet){
		Message message = new Message(packet);
		int clientKey;
		String to = "";
		String from = "";
		String content = "";
		String gameName = "";
		try {
			// Get info from message
			clientKey = message.nextInt();
			gameName = message.nextString();			
			to = message.nextString();
			from = message.nextString();
			content = message.nextString();
		} catch (Exception e){
			e.printStackTrace();	
		}
		try {
			
			String host = "localhost";
			
			// Get system properties
		    Properties properties = System.getProperties();

		    // Setup mail server
		    properties.setProperty("mail.smtp.host", host);

		    // Get the default Session object.
		    javax.mail.Session session = javax.mail.Session.getDefaultInstance(properties);
		      
			// Create a default MimeMessage object
	        MimeMessage email = new MimeMessage(session);

	        // Set From
	        email.setFrom(new InternetAddress(from));

	        // Set To
	        email.addRecipient(javax.mail.Message.RecipientType.TO,new InternetAddress(to));

	        // Set Subject
	        email.setSubject("Brains and Bets Game Request for Game: " + gameName);

	        // Set the actual message of the email
	        email.setText(content + "\nGame Name: " + gameName);

	        // Send message
	        javax.mail.Transport.send(email);
	        
	        if(VERBOSE){
	        	System.out.println("Email Sent...");
	        }
	    }catch (javax.mail.MessagingException mex) {
	         mex.printStackTrace();
	    }
		
	}
	
	public static void removeNetworkGame(int gameId) {
		networkGameThreads.remove(gameId);
	}
}
